package workflow

import (
	llq "github.com/emirpasic/gods/queues/linkedlistqueue"

	"mig/pkg/types"
	"mig/pkg/utils"
)

type RunWorkflowResult struct {
}

type Task struct {
	ID         string `json:"id" yaml:"id"`
	ConfigPath string `json:"config_path" yaml:"config_path"`
}

func (t *Task) load() *types.Config {
	return utils.MustLoad[*types.Config](t.ConfigPath)
}

type Dependency struct {
	SourceID string `json:"source_id" yaml:"source_id"`
	TargetID string `json:"target_id" yaml:"target_id"`
}

type Workflow struct {
	*workflowDAG
	Tasks        []*Task      `json:"tasks" yaml:"tasks"`
	Dependencies []Dependency `json:"dependencies" yaml:"dependencies"`
}

type workflowDAG struct {
	Roots []*workflowDAGNode
}

type workflowDAGNode struct {
	Task *Task
	Next []*workflowDAGNode
	Prev []*workflowDAGNode
}

func (n *workflowDAGNode) IsLeaf() bool {
	return len(n.Next) == 0
}

type workflowIterator struct {
	q *llq.Queue
}

func (w *Workflow) generateDAG() {
	if w.workflowDAG != nil || w.Tasks == nil {
		return
	}
	w.workflowDAG = &workflowDAG{}
	nodeMap := make(map[string]*workflowDAGNode)

	for _, task := range w.Tasks {
		nodeMap[task.ID] = &workflowDAGNode{
			Task: task,
			Next: []*workflowDAGNode{},
		}
	}

	for _, dep := range w.Dependencies {
		sourceNode, ok := nodeMap[dep.SourceID]
		if !ok {
			continue
		}
		targetNode, ok := nodeMap[dep.TargetID]
		if !ok {
			continue
		}
		sourceNode.Next = append(sourceNode.Next, targetNode)
		targetNode.Prev = append(targetNode.Prev, sourceNode)
	}

	inDegree := make(map[string]int)
	for _, task := range w.Tasks {
		inDegree[task.ID] = 0
	}
	for _, dep := range w.Dependencies {
		inDegree[dep.TargetID]++
	}
	for id, degree := range inDegree {
		if degree == 0 {
			if node, ok := nodeMap[id]; ok {
				w.workflowDAG.Roots = append(w.workflowDAG.Roots, node)
			}
		}
	}
}

func (w *Workflow) iterator() *workflowIterator {
	iter := &workflowIterator{
		q: llq.New(),
	}
	for _, root := range w.Roots {
		iter.q.Enqueue(root)
	}
	return iter
}

func (i *workflowIterator) HasNext() bool {
	return !i.q.Empty()
}

func (i *workflowIterator) Next() *Task {
	if i.q.Empty() {
		return nil
	}
	v, _ := i.q.Dequeue()
	n := v.(*workflowDAGNode)
	for _, nextTask := range n.Next {
		i.q.Enqueue(nextTask)
	}
	return n.Task
}

func (w *Workflow) Run() (*RunWorkflowResult, error) {
	w.generateDAG()
	iter := w.iterator()
	resultMap := map[*Task]*workerResult{}
	for iter.HasNext() {
		task := iter.Next()
		result, err := w.worker(task)
		if err != nil {
			return nil, err
		}
		resultMap[task] = result
	}
	return nil, nil
}
